import { isPlainObject } from 'lodash-es';
import {
    type UniversalIR,
    type UniversalDataType,
    type UniversalBigIntIR,
    type UniversalSymbolIR,
    type UniversalDateIR,
    type UniversalRegExpIR,
    type UniversalFunctionIR,
    type UniversalErrorIR,
    type UniversalWeakMapIR,
    type UniversalWeakSetIR,
    type UniversalArrayBufferIR,
    type UniversalTypedArrayIR,
    type UniversalArrayIR,
    type UniversalObjectIR,
    type UniversalMapIR,
    type UniversalSetIR,
    type UniversalCircularRefIR,
    type UniversalExecutionErrorWrapper,
    type UniversalObjectKeyIR,
    type UniversalObjectEntry
} from '@/models/universalTransferTypes';

// --- START: Base64 Decoding Helper (Uses browser 'atob') ---
const base64Decode = (function() {
    // Simple wrapper around browser's atob
    function base64ToBytes(base64: string): Uint8Array {
        try {
            const binaryString = atob(base64);
            const len = binaryString.length;
            const bytes = new Uint8Array(len);
            for (let i = 0; i < len; i++) {
                bytes[i] = binaryString.charCodeAt(i);
            }
            return bytes;
        } catch (e) {
            console.error('[Deserializer] Failed to decode Base64 string:', base64, e);
            throw new Error('Invalid Base64 string provided.');
        }
    }

    return {
        base64ToBytes: base64ToBytes,
        base64ToArrayBuffer: function(base64: string): ArrayBuffer {
            return base64ToBytes(base64).buffer;
        }
    };
})();
// --- END: Base64 Decoding Helper ---


/** State for tracking reconstructed objects during deserialization to handle cycles. */
interface DeserializationState {
  /** Map from reference ID (from IR) to the already reconstructed object/array/etc. */
  readonly referenceIdToReconstructedObject: Map<number, any>;
}

/**
 * Main deserialization function.
 * Converts data from the Universal Intermediate Representation (IR)
 * back into JavaScript objects/values, reconstructing special types and handling cycles.
 *
 * @param irData The IR data (result of JSON.parse on the serialized string).
 * @param state Internal state for tracking reconstructed objects. DO NOT provide manually.
 * @returns The deserialized JavaScript value.
 */
function deserializeFromUniversalIR(irData: UniversalIR, state?: DeserializationState): any {
  // Initialize state for the top-level call
  state = state || {
    referenceIdToReconstructedObject: new Map<number, any>(),
  };

  // 1. Handle non-object IR types (primitives, null)
  if (irData === null || typeof irData !== 'object') {
    return irData;
  }

  // 2. Handle untagged plain arrays and objects first
  if (!('__universalDataType__' in irData)) {
      if (Array.isArray(irData)) {
          // Assert irData is an array here
          const sourceArray = irData as any[];
          const deserializedArray: any[] = [];
          // Basic cycle handling for untagged arrays is missing.
          sourceArray.forEach((item: any, index: number) => {
              deserializedArray[index] = deserializeFromUniversalIR(item, state);
          });
          return deserializedArray;
      } else if (isPlainObject(irData)) {
          // Assert irData is a plain object here
          const sourceObject = irData as Record<string, any>;
          const deserializedObject: Record<string | symbol, any> = {};
          // Basic cycle handling for untagged objects missing.
          for (const key in sourceObject) {
              if (Object.prototype.hasOwnProperty.call(sourceObject, key)) {
                   deserializedObject[key] = deserializeFromUniversalIR(sourceObject[key], state);
              }
          }
          // Doesn't handle untagged symbol keys.
          return deserializedObject;
      } else {
          console.warn('[Deserializer] Encountered untagged, non-array/plain-object data:', irData);
          return irData; // Return complex non-plain objects as is if untagged
      }
  }

  // 3. Handle Tagged IR Objects
  // At this point, irData is guaranteed to be an object with __universalDataType__
  const dataType = irData.__universalDataType__;
  const refId = irData.__universalRefId__;

  // Handle Circular References
  if (dataType === 'CircularRef') {
      const circularRefId = (irData as UniversalCircularRefIR).__universalRefId__;
      if (state.referenceIdToReconstructedObject.has(circularRefId)) {
          return state.referenceIdToReconstructedObject.get(circularRefId);
      } else {
          console.error(`[Deserializer] Circular reference ID ${circularRefId} encountered before its definition.`);
          return { __universalError__: 'Unresolved circular reference', refId: circularRefId };
      }
  }

  // Check if this object (by refId) has already been reconstructed (due to cycles)
  if (typeof refId === 'number' && state.referenceIdToReconstructedObject.has(refId)) {
      return state.referenceIdToReconstructedObject.get(refId);
  }

  // --- Handle Specific Data Types Based on Tag --- //
  let reconstructedObject: any;
  let requiresPostPopulation = false; // Flag for objects needing population *after* storing in ref map

  switch (dataType) {
      case 'Undefined':
          reconstructedObject = undefined;
          break;
      case 'Null':
          reconstructedObject = null;
          break;
      case 'BigInt':
          try {
              reconstructedObject = BigInt((irData as UniversalBigIntIR).value);
          } catch (e) {
              console.error('[Deserializer] Failed to parse BigInt:', irData, e);
              reconstructedObject = { __universalError__: 'Invalid BigInt value', originalValue: (irData as UniversalBigIntIR).value };
          }
          break;
      case 'Symbol':
          reconstructedObject = Symbol((irData as UniversalSymbolIR).description);
          break;
      case 'Date':
          reconstructedObject = new Date((irData as UniversalDateIR).value);
          if (isNaN(reconstructedObject.getTime())) {
              console.error('[Deserializer] Failed to parse Date:', irData);
              reconstructedObject = { __universalError__: 'Invalid Date value', originalValue: (irData as UniversalDateIR).value };
          }
          break;
      case 'RegExp':
          try {
              const regExpIr = irData as UniversalRegExpIR;
              reconstructedObject = new RegExp(regExpIr.source, regExpIr.flags);
          } catch (e) {
              console.error('[Deserializer] Failed to parse RegExp:', irData, e);
              reconstructedObject = { __universalError__: 'Invalid RegExp', originalValue: irData };
          }
          break;
      case 'Function':
          reconstructedObject = {
              __universalType__: 'Function',
              source: (irData as UniversalFunctionIR).source,
              toString: () => (irData as UniversalFunctionIR).source || '[Function source unavailable]',
          };
          break;
      case 'Error':
          const errorIr = irData as UniversalErrorIR;
          console.log('[Deserializer] Processing Error IR:', JSON.stringify(errorIr, null, 2));
          reconstructedObject = new Error(errorIr.message);
          reconstructedObject.name = errorIr.name;
          reconstructedObject.stack = errorIr.stack;
          if ('cause' in errorIr && errorIr.cause !== undefined) {
              console.log('[Deserializer] Found cause in IR, attempting to deserialize...');
              try {
                  const deserializedCause = deserializeFromUniversalIR(errorIr.cause, state);
                  console.log('[Deserializer] Deserialized cause:', deserializedCause);
                  reconstructedObject.cause = deserializedCause;
              } catch (causeError) {
                  console.error('[Deserializer] Failed to deserialize error cause:', errorIr.cause, causeError);
                  reconstructedObject.cause = { __universalError__: 'Failed to deserialize cause', originalCause: errorIr.cause };
              }
          } else {
              console.log('[Deserializer] No cause found in Error IR.');
          }
          console.log('[Deserializer] Final reconstructed Error object:', reconstructedObject);
          break;
      case 'WeakMap':
          reconstructedObject = new WeakMap();
          break;
      case 'WeakSet':
          reconstructedObject = new WeakSet();
          break;
      case 'ArrayBuffer':
          try {
              reconstructedObject = base64Decode.base64ToArrayBuffer((irData as UniversalArrayBufferIR).base64);
          } catch (e) {
              console.error('[Deserializer] Failed to decode ArrayBuffer:', irData, e);
              reconstructedObject = { __universalError__: 'Invalid ArrayBuffer Base64', originalValue: irData };
          }
          break;
      case 'TypedArray':
          try {
              const typedArrayIr = irData as UniversalTypedArrayIR;
              const buffer = base64Decode.base64ToArrayBuffer(typedArrayIr.base64);
              const Constructor = (globalThis as any)[typedArrayIr.constructorName];
              if (typeof Constructor === 'function') {
                  reconstructedObject = new Constructor(buffer);
              } else {
                  throw new Error(`Unsupported TypedArray constructor: ${typedArrayIr.constructorName}`);
              }
          } catch (e) {
              console.error('[Deserializer] Failed to decode TypedArray:', irData, e);
              reconstructedObject = { __universalError__: 'Invalid TypedArray data or constructor', originalValue: irData };
          }
          break;
      case 'Array':
          reconstructedObject = []; // Create instance first
          requiresPostPopulation = true;
          break;
      case 'Object':
          reconstructedObject = {}; // Create instance first
          requiresPostPopulation = true;
          break;
      case 'Map':
          reconstructedObject = new Map(); // Create instance first
          requiresPostPopulation = true;
          break;
      case 'Set':
          reconstructedObject = new Set(); // Create instance first
          requiresPostPopulation = true;
          break;
      default:
          console.warn(`[Deserializer] Encountered unknown universal data type tag: ${dataType}`, irData);
          reconstructedObject = { __universalError__: 'Unknown data type tag', originalValue: irData };
          break;
  }

  // --- Store the newly created object instance in the reference map BEFORE populating --- //
  if (typeof refId === 'number') {
      if (!state.referenceIdToReconstructedObject.has(refId)) {
           state.referenceIdToReconstructedObject.set(refId, reconstructedObject);
      }
  }

  // --- Post-population for complex types (Array, Object, Map, Set) --- //
  if (requiresPostPopulation) {
      try {
          switch (dataType) {
              case 'Array':
                  const arrayIr = irData as UniversalArrayIR;
                  arrayIr.values.forEach((itemIR: UniversalIR) => {
                      (reconstructedObject as any[]).push(deserializeFromUniversalIR(itemIR, state));
                  });
                  break;
              case 'Object':
                  const objectIr = irData as UniversalObjectIR;
                  objectIr.entries.forEach(([keyIR, valueIR]: UniversalObjectEntry) => {
                      const key = typeof keyIR === 'string' ? keyIR : deserializeFromUniversalIR(keyIR, state);
                      const value = deserializeFromUniversalIR(valueIR, state);
                      if (typeof key === 'string' || typeof key === 'symbol') {
                           reconstructedObject[key] = value;
                      } else {
                           console.warn('[Deserializer] Skipping object entry with invalid deserialized key type:', key);
                      }
                  });
                  break;
              case 'Map':
                  const mapIr = irData as UniversalMapIR;
                  mapIr.entries.forEach(([keyIR, valueIR]: [UniversalIR, UniversalIR]) => {
                      const key = deserializeFromUniversalIR(keyIR, state);
                      const value = deserializeFromUniversalIR(valueIR, state);
                      (reconstructedObject as Map<any, any>).set(key, value);
                  });
                  break;
              case 'Set':
                  const setIr = irData as UniversalSetIR;
                  setIr.values.forEach((valueIR: UniversalIR) => {
                      const value = deserializeFromUniversalIR(valueIR, state);
                      (reconstructedObject as Set<any>).add(value);
                  });
                  break;
          }
      } catch (populationError) {
          console.error(`[Deserializer] Error during post-population for type ${dataType}:`, populationError, irData);
          return { __universalError__: `Failed to populate ${dataType}`, originalValue: irData, cause: populationError };
      }
  }

  return reconstructedObject;
}

/**
 * Parses the JSON string received from the QuickJS service and deserializes it
 * from the Universal Intermediate Representation (IR) into rich JavaScript objects.
 * Handles top-level execution errors encoded in the wrapper.
 *
 * @param jsonString The JSON string containing the serialized IR.
 * @returns The deserialized data, or an error object if execution/parsing/deserialization failed.
 */
export function parseAndDeserializeQuickJsResult(jsonString: string): any {
    let parsedIr: any;
    try {
        if (typeof jsonString !== 'string' || !jsonString.trim()) {
            console.warn('[Deserializer] Received empty or non-string input. Returning null.');
            return null;
        }
        parsedIr = JSON.parse(jsonString);
    } catch (parseError) {
        console.error('[Deserializer] Failed to parse JSON from QuickJS:', parseError);
        console.error('[Deserializer] Received string was:', jsonString.substring(0, 500) + (jsonString.length > 500 ? '...' : ''));
        const error = new Error('Failed to parse result JSON from QuickJS execution.');
        (error as any).cause = parseError;
        return { __universalExecutionError__: error };
    }

    try {
        if (parsedIr && typeof parsedIr === 'object' && parsedIr.__universalExecutionError__) {
            console.error('[Deserializer] QuickJS script execution failed (reported via wrapper).');
            const deserializedError = deserializeFromUniversalIR(parsedIr.__universalExecutionError__);
            return { __universalExecutionError__: deserializedError };
        }

        return deserializeFromUniversalIR(parsedIr);

    } catch (deserializeError) {
        console.error('[Deserializer] Failed during IR deserialization:', deserializeError);
        console.error('[Deserializer] Parsed IR was:', parsedIr);
        const error = new Error('Failed during IR deserialization process.');
        (error as any).cause = deserializeError;
        return { __universalExecutionError__: error };
    }
} 